
/* 05_texture_sampling.c - OpenGL-based very simple vertex program example
   using Cg program from Chapter 2 of "The Cg Tutorial" (Addison-Wesley,
   ISBN 0321194969). */

#include <windows.h>
#include <stdio.h>
#include <d3d9.h>     /* Can't include this?  Is DirectX SDK installed? */
#include <Cg/cg.h>    /* Can't include this?  Is Cg Toolkit installed! */
#include <Cg/cgD3D9.h>

#include "DXUT.h"  /* DirectX Utility Toolkit (part of the DirectX SDK) */
#include "spr.h"

#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")

#define KD_SZ_TEST_SPR "wing_lv1_l.spr"
//#define KD_SZ_TEST_SPR "test_spr.spr"
static size_t	g_nWndWidth = 400;
static size_t	g_nWndHeight = 400;
static int		g_nCurFrame = 0;
static int		g_nIndex = 0;
static Spr		g_Spr;

static CGcontext   myCgContext;
static CGprofile   myCgVertexProfile,
                   myCgFragmentProfile;
static CGprogram   myCgVertexProgram,
                   myCgFragmentProgram;
// texture param
static CGparameter myCgFragmentParam_Frame;
static CGparameter myCgFragmentParam_Pattle;
static CGparameter myCgFragmentParam_Replace;

static PDIRECT3DVERTEXBUFFER9 myVertexBuffer = NULL;

static const WCHAR *myProgramNameW = L"05_texture_sampling";
static const char *myProgramName = "05_texture_sampling",
                  *myVertexProgramFileName = "C3E2v_varying.cg",
/* Page 65 */     *myVertexProgramName = "C3E2v_varying",
                  *myFragmentProgramFileName = "C3E3f_texture.cg",
/* Page 67 */     *myFragmentProgramName = "C3E3f_texture";

static void checkForCgError(const char *situation)
{
  char buffer[4096];
  CGerror error;
  const char *string = cgGetLastErrorString(&error);
  
  if (error != CG_NO_ERROR) {
    if (error == CG_COMPILER_ERROR) {
      sprintf(buffer,
              "Program: %s\n"
              "Situation: %s\n"
              "Error: %s\n\n"
              "Cg compiler output...\n",
              myProgramName, situation, string);
      OutputDebugStringA(buffer);
      OutputDebugStringA(cgGetLastListing(myCgContext));
      sprintf(buffer,
              "Program: %s\n"
              "Situation: %s\n"
              "Error: %s\n\n"
              "Check debug output for Cg compiler output...",
              myProgramName, situation, string);
      MessageBoxA(0, buffer,
                  "Cg compilation error", MB_OK | MB_ICONSTOP | MB_TASKMODAL);
    } else {
      sprintf(buffer,
              "Program: %s\n"
              "Situation: %s\n"
              "Error: %s",
              myProgramName, situation, string);
      MessageBoxA(0, buffer,
                  "Cg runtime error", MB_OK | MB_ICONSTOP | MB_TASKMODAL);
    }
    exit(1);
  }
}

/* Forward declared DXUT callbacks registered by WinMain. */
static HRESULT CALLBACK OnResetDevice(IDirect3DDevice9*, const D3DSURFACE_DESC*, void*);
static void CALLBACK OnFrameRender(IDirect3DDevice9*, double, float, void*);
static void CALLBACK OnLostDevice(void*);
static void CALLBACK OnKeyboard(UINT nChar, bool bKeyDown, bool bAltDown, void* pUserContext);
static void UpdateVertexBuffer();

INT WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
  myCgContext = cgCreateContext();
  checkForCgError("creating context");
  cgSetParameterSettingMode(myCgContext, CG_DEFERRED_PARAMETER_SETTING);

  DXUTSetCallbackDeviceReset(OnResetDevice);
  DXUTSetCallbackDeviceLost(OnLostDevice);
  DXUTSetCallbackFrameRender(OnFrameRender);
  DXUTSetCallbackKeyboard(OnKeyboard);

  /* Parse  command line, handle  default hotkeys, and show messages. */
  DXUTInit();

  DXUTCreateWindow(myProgramNameW);

  /* Display 400x400 window. */
  DXUTCreateDevice(D3DADAPTER_DEFAULT, true, g_nWndWidth, g_nWndHeight);

  DXUTMainLoop();

  cgDestroyProgram(myCgVertexProgram);
  checkForCgError("destroying vertex program");
  cgDestroyProgram(myCgFragmentProgram);
  checkForCgError("destroying fragment program");
  cgDestroyContext(myCgContext);

  return DXUTGetExitCode();
}

static void createCgPrograms()
{
  const char **profileOpts;

  /* Determine the best profiles once a device to be set. */
  myCgVertexProfile = cgD3D9GetLatestVertexProfile();
  checkForCgError("getting latest vertex profile");

  profileOpts = cgD3D9GetOptimalOptions(myCgVertexProfile);
  checkForCgError("getting latest vertex profile options");

  myCgVertexProgram =
    cgCreateProgramFromFile(
      myCgContext,              /* Cg runtime context */
      CG_SOURCE,                /* Program in human-readable form */
      myVertexProgramFileName,  /* Name of file containing program */
      myCgVertexProfile,        /* Profile: OpenGL ARB vertex program */
      myVertexProgramName,      /* Entry function name */
      profileOpts);             /* Pass optimal compiler options */
  checkForCgError("creating vertex program from file");

  /* Determine the best profile once a device to be set. */
  myCgFragmentProfile = cgD3D9GetLatestPixelProfile();
  checkForCgError("getting latest fragment profile");

  profileOpts = cgD3D9GetOptimalOptions(myCgFragmentProfile);
  checkForCgError("getting latest fragment profile options");

  myCgFragmentProgram =
    cgCreateProgramFromFile(
      myCgContext,                /* Cg runtime context */
      CG_SOURCE,                  /* Program in human-readable form */
      myFragmentProgramFileName,  /* Name of file containing program */
      myCgFragmentProfile,        /* Profile: OpenGL ARB vertex program */
      myFragmentProgramName,      /* Entry function name */
      profileOpts);               /* No extra compiler options */
  checkForCgError("creating fragment program from file");

  myCgFragmentParam_Frame =
    cgGetNamedParameter(myCgFragmentProgram, "frameTex");
  checkForCgError("getting decal parameter");

  myCgFragmentParam_Pattle = cgGetNamedParameter(myCgFragmentProgram, "palletTex");
  checkForCgError("getting decal parameter");

  myCgFragmentParam_Replace = cgGetNamedParameter(myCgFragmentProgram, "replaceColor");
  checkForCgError("getting decal parameter");
}

struct MY_V3F_T2F
{
  FLOAT x, y, z;
  FLOAT s, t;
  MY_V3F_T2F(FLOAT _x = 0.0f, FLOAT _y = 0.0f, FLOAT _z = 0.0f, float _s = 0.0f, FLOAT _t = 0.0f)
	  : x(_x), y(_y), z(_z), s(_s), t(_t)
  {  }

  static const DWORD fvf = D3DFVF_XYZ|D3DFVF_TEX1;
};

static HRESULT initVertexBuffer(IDirect3DDevice9* pDev)
{
  /* Initialize three vertices for rendering a triangle. */
  if (FAILED(pDev->CreateVertexBuffer(sizeof(MY_V3F_T2F) * 4,
                                      0, D3DFVF_XYZ|D3DFVF_TEX0,
                                      D3DPOOL_DEFAULT,
                                      &myVertexBuffer, NULL))) {
    return E_FAIL;
  }

  void* pVertices;
  if (FAILED(myVertexBuffer->Lock(0, 0, /* map entire buffer */
                                  &pVertices, 0))) {
    return E_FAIL;
  }
  memset(pVertices, 0, sizeof(MY_V3F_T2F) * 4);
  myVertexBuffer->Unlock();

  return S_OK;
}


static HRESULT initTexture(IDirect3DDevice9* pDev)
{
  return S_OK;
}

static HRESULT CALLBACK OnResetDevice(IDirect3DDevice9* pDev, 
                                      const D3DSURFACE_DESC* backBuf,
                                      void* userContext)
{
  cgD3D9SetDevice(pDev);
  checkForCgError("setting Direct3D device");

  static int firstTime = 1;
  if (firstTime) {
    /* Cg runtime resources such as CGprogram and CGparameter handles
       survive a device reset so we just need to compile a Cg program
       just once.  We do however need to unload Cg programs with
       cgD3DUnloadProgram upon when a Direct3D device is lost and load
       Cg programs every Direct3D device reset with cgD3D9UnloadProgram. */
    createCgPrograms();
    firstTime = 0;
  }

  /* false below means "with parameter shadowing" */
  cgD3D9LoadProgram(myCgVertexProgram, false, 0);
  checkForCgError("loading vertex program");

  /* false below means "with parameter shadowing" */
  cgD3D9LoadProgram(myCgFragmentProgram, false, 0);
  checkForCgError("loading fragment program");

  if (FAILED(initVertexBuffer(pDev))) {
    return E_FAIL;
  }

  // test
  if (false == g_Spr.Load(pDev, KD_SZ_TEST_SPR))
	  return E_FAIL;

  UpdateVertexBuffer();
  return S_OK;
}

static void CALLBACK OnFrameRender(IDirect3DDevice9* pDev,
                                   double time,
                                   float elapsedTime,
                                   void* userContext)
{
  pDev->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
              D3DCOLOR_ARGB(0, 40, 40, 40), 1.0f, 0);

  if (SUCCEEDED(pDev->BeginScene()))
  {
	  if (g_Spr.GetFrameCount())
	  {
		  cgD3D9BindProgram(myCgVertexProgram);
		  checkForCgError("binding vertex program");

		  static FLOAT aReplace[4] = {1.0f, 0.0f, 0.0f, 1.f};
		  aReplace[3] = (float)g_nIndex;
		  static DWORD dwCurTime = GetTickCount();
		  if (GetTickCount() - dwCurTime > 100)
		  {
			  g_nIndex = (++ g_nIndex) % 256;
			  dwCurTime = GetTickCount();
		  }

		  cgD3D9SetTexture(myCgFragmentParam_Frame, g_Spr.GetFrameTexture(g_nCurFrame));
		  cgD3D9SetTexture(myCgFragmentParam_Pattle, g_Spr.GetPalletTexture());
		  cgSetParameter4fv(myCgFragmentParam_Replace, aReplace);

		  cgD3D9BindProgram(myCgFragmentProgram);
		  checkForCgError("binding fragment program");

		  /* Render the stars. */
		  pDev->SetStreamSource(0, myVertexBuffer, 0, sizeof(MY_V3F_T2F));
		  pDev->SetFVF(MY_V3F_T2F::fvf);

		  //pDev->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
		  //pDev->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
		  //pDev->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
		  pDev->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
		  pDev->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
		  pDev->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_POINT);
		  pDev->SetSamplerState(1, D3DSAMP_MINFILTER, D3DTEXF_POINT);
		  pDev->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
		  pDev->SetSamplerState(1, D3DSAMP_MIPFILTER, D3DTEXF_POINT);

		  pDev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
		  pDev->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
		  pDev->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
		  pDev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
		  pDev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

		  pDev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
	  }
	  pDev->EndScene();
  }
  D3DXCOLOR colr;
}

static void CALLBACK OnLostDevice(void* userContext)
{
  myVertexBuffer->Release();
  g_Spr.Unload();
  g_nCurFrame = 0;
  cgD3D9SetDevice(NULL);
}

static void UpdateVertexBuffer()
{
	if (NULL == myVertexBuffer)
		return;

	IDirect3DTexture9* pTexture = g_Spr.GetFrameTexture(g_nCurFrame);
	if (NULL == pTexture)
		throw std::exception("Get Frame Texture Failed...");
	
	D3DSURFACE_DESC desc;
	pTexture->GetLevelDesc(0, &desc);
	MY_V3F_T2F* pVertex = NULL;
	if (SUCCEEDED(myVertexBuffer->Lock(0, 0, (void**)&pVertex, 0)))
	{
		float x = (float)desc.Width / (float)g_nWndWidth;
		float y = (float)desc.Height / (float)g_nWndHeight;
		//float x = 0.8f;
		//float y = 0.8f;
		float left		= -x;
		float top		= -y;
		float right		= x;
		float bottom	= y;
		// left_top
		pVertex[0] = MY_V3F_T2F(left, top);
		// right_top
		pVertex[1] = MY_V3F_T2F(right, top, 0.0f, 1.0f, 0.0f);
		// left_bottom
		pVertex[2] = MY_V3F_T2F(left, bottom, 0.0f, 0.0f, 1.0f);
		// right_bottom
		pVertex[3] = MY_V3F_T2F(right, bottom, 0.0f, 1.0f, 1.0f);
		myVertexBuffer->Unlock();
	}
}

static void CALLBACK OnKeyboard(UINT nChar, bool bKeyDown, bool bAltDown, void* pUserContext)
{
	if (bKeyDown && g_Spr.GetFrameCount())
	{
		switch (nChar)
		{
		case VK_LEFT:
			g_nCurFrame = g_nCurFrame > 0 ? (g_nCurFrame - 1) : (int)g_Spr.GetFrameCount() - 1;
			UpdateVertexBuffer();
			break;
		case VK_RIGHT:
			g_nCurFrame = g_nCurFrame >= (int)g_Spr.GetFrameCount() - 1 ? 0 : (g_nCurFrame + 1);
			UpdateVertexBuffer();
			break;
		case VK_UP:
			g_nIndex = g_nIndex > 0 ? (g_nIndex - 1) : 256;
			break;
		case VK_DOWN:
			g_nIndex = g_nIndex >= 256 ? (g_nIndex + 1) : 0;
			break;
		}
	}
}
